#pragma once

#include "ConvexHullPoint.h"
#include <vector>
#include <ClanLib/core.h>
#include <cmath>
#include "ConvexHull.h"
#include <cassert>
#include "ConvexHullMerger.h"

enum ConvexHullWorkUnitState
{
	Initial,
	WaitingForChildren,
	Merging,
	Finished,
};

class ConvexHullWorkUnit
{
public:
	inline ConvexHullWorkUnit(const std::vector<clan::Vec2d>& points, int startIndex, int endindex);
	inline std::vector<clan::Vec2d> GetPointsOfInterest(void) const;
	inline ConvexHullWorkUnitState GetState(void) const;
	inline bool HullIsBruteForceComputable( void ) const;
	inline void GenerateChildren( void );
	inline ConvexHullWorkUnit* GetChildLeft( void );
	inline ConvexHullWorkUnit* GetChildRight( void );
	inline void BruteForceComputeHull( void );
	inline std::vector<clan::Line2d> GetConvexHullLines( void );
	inline void StartChildrenMerger( void );
	inline void StepMerge( void );

private:
	inline void m_UpdateTangents(void);

	ConvexHullWorkUnitState m_state;
	ConvexHullWorkUnit* m_childLeft;
	ConvexHullWorkUnit* m_childRight;
	ConvexHull* m_hull;
	const std::vector<clan::Vec2d>& m_points;
	std::vector<clan::Line2d> m_convexHullLines;
	std::vector<clan::Line2d> m_tangents;
	int m_startIndex;
	int m_endIndex;
	ConvexHullMerger* m_merger;
};

inline ConvexHullWorkUnit::ConvexHullWorkUnit(const std::vector<clan::Vec2d>& points,
											  int startIndex,
											  int endIndex)
											  : m_state(ConvexHullWorkUnitState::Initial),
											  m_childLeft(nullptr),
											  m_childRight(nullptr),
											  m_hull(nullptr),
											  m_points(points),
											  m_startIndex(startIndex),
											  m_endIndex(endIndex),
											  m_merger(nullptr),
											  m_tangents()
{

}

inline std::vector<clan::Vec2d> ConvexHullWorkUnit::GetPointsOfInterest(void) const
{
	return std::vector<clan::Vec2d>(m_points.begin() + m_startIndex, m_points.begin() + m_endIndex + 1);
}

inline ConvexHullWorkUnitState ConvexHullWorkUnit::GetState( void ) const
{
	return m_state;
}

inline bool ConvexHullWorkUnit::HullIsBruteForceComputable( void ) const
{
	return ConvexHull::IsBruteForceComputable(m_startIndex, m_endIndex);
}

void ConvexHullWorkUnit::GenerateChildren( void )
{
	m_childLeft = new ConvexHullWorkUnit(m_points, m_startIndex, static_cast<int>(floor((m_startIndex + m_endIndex) / 2.)));
	m_childRight = new ConvexHullWorkUnit(m_points, static_cast<int>(floor((m_startIndex + m_endIndex) / 2.)) + 1, m_endIndex);
	m_state = ConvexHullWorkUnitState::WaitingForChildren;
}

ConvexHullWorkUnit* ConvexHullWorkUnit::GetChildRight( void )
{
	return m_childRight;
}

ConvexHullWorkUnit* ConvexHullWorkUnit::GetChildLeft( void )
{
	return m_childLeft;
}

void ConvexHullWorkUnit::BruteForceComputeHull(void)
{
	m_hull = new ConvexHull(m_points, m_startIndex, m_endIndex);
	m_convexHullLines = m_hull->GetLines();
	m_state = ConvexHullWorkUnitState::Finished;
}

std::vector<clan::Line2d> ConvexHullWorkUnit::GetConvexHullLines( void )
{
#if _DEBUG
	if(m_state == ConvexHullWorkUnitState::Initial)
	{
		assert(m_convexHullLines.empty());
	}
#endif
	if (m_state== WaitingForChildren)
	{
		if(m_childLeft->GetState() == Finished)
		{
			m_convexHullLines.insert(m_convexHullLines.begin(), m_childLeft->m_convexHullLines.begin(), m_childLeft->m_convexHullLines.end());
			m_childLeft->GetConvexHullLines().clear();
		}
		if(m_childRight->GetState() == Finished)
		{
			m_convexHullLines.insert(m_convexHullLines.begin(), m_childRight->m_convexHullLines.begin(), m_childRight->m_convexHullLines.end());
			m_childRight->GetConvexHullLines().clear();
		}
	}
	std::vector<clan::Line2d> lines(m_convexHullLines.begin(), m_convexHullLines.end());
	if (m_state == Merging)
	{
		lines.insert(lines.end(), m_tangents.begin(), m_tangents.end());
	}
	return lines;
}

inline void ConvexHullWorkUnit::m_UpdateTangents( void )
{
	m_tangents.clear();
	ConvexHullLine tangent = m_merger->GetLowerTangent();
	m_tangents.push_back(clan::Line2d(tangent.left->location, tangent.right->location));
	tangent = m_merger->GetUpperTangent();
	m_tangents.push_back(clan::Line2d(tangent.left->location, tangent.right->location));
}

inline void ConvexHullWorkUnit::StartChildrenMerger(void)
{
	assert(m_childLeft->GetState() == ConvexHullWorkUnitState::Finished
		&& m_childRight->GetState() == ConvexHullWorkUnitState::Finished);
	m_merger = new ConvexHullMerger(m_childLeft->m_hull, m_childRight->m_hull);
	m_state = ConvexHullWorkUnitState::Merging;
	delete m_childLeft;
	delete m_childRight;
	m_UpdateTangents();
}

void ConvexHullWorkUnit::StepMerge( void )
{
	if(m_merger->isFinished())
	{
		m_hull = m_merger->GetMergedHull();
		m_tangents.clear();
		m_convexHullLines = m_hull->GetLines();
		m_state = Finished;
	}
	else
	{
		m_merger->Step();
	}
	m_UpdateTangents();
}
